package com.yeskery.nut.bean.scheduling;

import com.yeskery.nut.annotation.scheduling.Scheduled;
import com.yeskery.nut.bean.ApplicationContext;
import com.yeskery.nut.core.Environment;
import com.yeskery.nut.core.ThreadPool;
import com.yeskery.nut.util.CronSequenceGenerator;
import com.yeskery.nut.util.ExpressionUtils;
import com.yeskery.nut.util.StringUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.TimeZone;

/**
 * 定时调度执行器
 * @author sprout
 * @version 1.0
 * 2024-06-04 23:16
 */
public class ScheduledExecutor {

    /** 应用上下文 */
    private final ApplicationContext applicationContext;

    /**
     * 构建定时调度执行器
     * @param applicationContext 应用上下文
     */
    public ScheduledExecutor(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }

    /** 定时调度元数据集合 */
    private final List<ScheduledMetadata> scheduledMetadataList = new ArrayList<>();

    /**
     * 添加定时调度执行器元数据
     * @param scheduledMetadata 定时调度执行器元数据
     */
    public void addScheduledMetadata(ScheduledMetadata scheduledMetadata) {
        scheduledMetadataList.add(scheduledMetadata);
    }

    /**
     * 开始执行调度计划
     */
    public void start() {
        for (ScheduledExecuteMetadata scheduledExecuteMetadata : parseScheduledExecuteMetadataList()) {
           execute(scheduledExecuteMetadata);
        }
    }

    /**
     * 执行调度任务
     * @param scheduledExecuteMetadata 定时调度执行元数据
     */
    public void execute(ScheduledExecuteMetadata scheduledExecuteMetadata) {
        validScheduledExecuteMetadata(scheduledExecuteMetadata);
        ThreadPool threadPool = applicationContext.getBean(ThreadPool.class);
        ScheduledMetadata scheduledMetadata = scheduledExecuteMetadata.getScheduledMetadata();
        if (scheduledExecuteMetadata.getType() == ScheduleExecuteType.IMMEDIATELY) {
            try {
                new ImmediatelyScheduledJob().handle(threadPool, () -> {
                    try {
                        scheduledMetadata.getMethod().invoke(scheduledMetadata.getBean());
                    } catch (Exception e) {
                        throw new ScheduledException("ScheduledJob Execute Fail.", e);
                    }
                });
                return;
            } catch (Exception e) {
                throw new ScheduledException("ScheduledJob Execute Fail.", e);
            }
        } else if (scheduledExecuteMetadata.getType() == ScheduleExecuteType.INITIAL_DELAY) {
            try {
                long initialDelay = scheduledExecuteMetadata.getInitialDelay();
                initialDelay = initialDelay < 0 ? 0: initialDelay;
                new InitialDelayScheduledJob(initialDelay).handle(threadPool, () -> {
                    try {
                        scheduledMetadata.getMethod().invoke(scheduledMetadata.getBean());
                    } catch (Exception e) {
                        throw new ScheduledException("ScheduledJob Execute Fail.", e);
                    }
                });
                return;
            } catch (Exception e) {
                throw new ScheduledException("ScheduledJob Execute Fail.", e);
            }
        }
        executeScheduledJob(threadPool, scheduledExecuteMetadata);
    }

    /**
     * 执行调度任务
     * @param threadPool 线程池对象
     * @param scheduledExecuteMetadata 定时调度执行元数据
     */
    private void executeScheduledJob(ThreadPool threadPool, ScheduledExecuteMetadata scheduledExecuteMetadata) {
        ScheduledJob scheduledJob;
        if (scheduledExecuteMetadata.getType() == ScheduleExecuteType.FIXED_DELAY) {
            scheduledJob = new FixedDelayScheduledJob(scheduledExecuteMetadata.getFixedDelay());
        } else if (scheduledExecuteMetadata.getType() == ScheduleExecuteType.FIXED_RATE) {
            scheduledJob = new FixedRateScheduleJob(scheduledExecuteMetadata.getFixedRate());
        } else if (scheduledExecuteMetadata.getType() == ScheduleExecuteType.CRON) {
            scheduledJob = new CronScheduledJob(scheduledExecuteMetadata.getCronSequenceGenerator());
        } else {
            throw new ScheduledException("Invalid Schedule Type.");
        }
        if (scheduledExecuteMetadata.getInitialDelay() > 0) {
            scheduledJob = new WrapInitialDelayScheduledJob(scheduledExecuteMetadata.getInitialDelay(), scheduledJob);
        }
        try {
            scheduledJob.handle(threadPool, () -> {
                try {
                    scheduledExecuteMetadata.getScheduledMetadata().getMethod().invoke(scheduledExecuteMetadata.getScheduledMetadata().getBean());
                } catch (Exception e) {
                    throw new ScheduledException("ScheduledJob Execute Fail.", e);
                }
            });
        } catch (Exception e) {
            throw new ScheduledException("ScheduledJob Execute Fail.", e);
        }
    }

    /**
     * 转换定时调度执行元数据集合
     * @return 转换后的定时调度执行元数据集合
     */
    private List<ScheduledExecuteMetadata> parseScheduledExecuteMetadataList() {
        List<ScheduledExecuteMetadata> scheduledExecuteMetadataList = new ArrayList<>(scheduledMetadataList.size());
        Environment environment = applicationContext.getBean(Environment.class);
        for (ScheduledMetadata scheduledMetadata : scheduledMetadataList) {
            scheduledExecuteMetadataList.add(buildScheduledExecuteMetadata(environment, scheduledMetadata));
        }
        return scheduledExecuteMetadataList;
    }

    /**
     * 构建定时调度执行元数据，优先级：fixedDelay -> fixedRate -> cron
     * @param environment 环境对象
     * @param scheduledMetadata 定时调度元数据
     * @return 定时调度执行元数据
     */
    private ScheduledExecuteMetadata buildScheduledExecuteMetadata(Environment environment, ScheduledMetadata scheduledMetadata) {
        ScheduledExecuteMetadata scheduledExecuteMetadata = new ScheduledExecuteMetadata();
        scheduledExecuteMetadata.setScheduledMetadata(scheduledMetadata);
        Scheduled scheduled = scheduledMetadata.getScheduled();
        String initialDelayString = scheduled.initialDelayString().trim();
        long initialDelay = scheduled.initialDelay();
        if (!StringUtils.isEmpty(initialDelayString) || initialDelay != -1L) {
            if (!StringUtils.isEmpty(initialDelayString)) {
                try {
                    initialDelay = Long.parseLong(ExpressionUtils.getExpressValue(environment, initialDelayString,
                            () -> new ScheduledException("@Scheduled On Class[" + scheduledMetadata.getBean().getClass().getName()
                                    + "] Method[" + scheduledMetadata.getMethod().getName() + "] initialDelayString() Expression["
                                    + initialDelayString + "] Not Exist.")));
                } catch (NumberFormatException e) {
                    throw new ScheduledException("@Scheduled On Class[" + scheduledMetadata.getBean().getClass().getName()
                            + "] Method[" + scheduledMetadata.getMethod().getName() + "] fixedDelayString() Expression["
                            + initialDelayString + "] Invalid.", e);
                }
            }
        }
        scheduledExecuteMetadata.setInitialDelay(initialDelay);

        String fixedDelayString = scheduled.fixedDelayString().trim();
        long fixedDelay = scheduled.fixedDelay();
        if (!StringUtils.isEmpty(fixedDelayString) || fixedDelay != -1L) {
            if (!StringUtils.isEmpty(fixedDelayString)) {
                try {
                    fixedDelay = Long.parseLong(ExpressionUtils.getExpressValue(environment, fixedDelayString,
                            () -> new ScheduledException("@Scheduled On Class[" + scheduledMetadata.getBean().getClass().getName()
                                    + "] Method[" + scheduledMetadata.getMethod().getName()
                                    + "] fixedDelayString() Expression[" + fixedDelayString + "] Not Exist.")));
                } catch (NumberFormatException e) {
                    throw new ScheduledException("@Scheduled On Class[" + scheduledMetadata.getBean().getClass().getName()
                            + "] Method[" + scheduledMetadata.getMethod().getName()
                            + "] fixedDelayString() Expression[" + fixedDelayString + "] Invalid.", e);
                }
            }
            scheduledExecuteMetadata.setType(ScheduleExecuteType.FIXED_DELAY);
            scheduledExecuteMetadata.setFixedDelay(fixedDelay);
            return scheduledExecuteMetadata;
        }

        String fixedRateString = scheduled.fixedRateString().trim();
        long fixedRate = scheduled.fixedRate();
        if (!StringUtils.isEmpty(fixedRateString) || fixedRate != -1L) {
            if (!StringUtils.isEmpty(fixedRateString)) {
                try {
                    fixedRate = Long.parseLong(ExpressionUtils.getExpressValue(environment, fixedRateString,
                            () -> new ScheduledException("@Scheduled On Class[" + scheduledMetadata.getBean().getClass().getName()
                                    + "] Method[" + scheduledMetadata.getMethod().getName()
                                    + "] fixedRateString() Expression[" + fixedRateString + "] Not Exist.")));
                } catch (NumberFormatException e) {
                    throw new ScheduledException("@Scheduled On Class[" + scheduledMetadata.getBean().getClass().getName()
                            + "] Method[" + scheduledMetadata.getMethod().getName()
                            + "] fixedRateString() Expression[" + fixedRateString + "] Invalid.", e);
                }
            }
            scheduledExecuteMetadata.setType(ScheduleExecuteType.FIXED_RATE);
            scheduledExecuteMetadata.setFixedRate(fixedRate);
            return scheduledExecuteMetadata;
        }

        String cron = scheduled.cron().trim();
        String zone = scheduled.zone().trim();
        if (!StringUtils.isEmpty(cron)) {
            String value = ExpressionUtils.getExpressValue(environment, cron,
                    () -> new ScheduledException("@Scheduled On Class[" + scheduledMetadata.getBean().getClass().getName()
                            + "] Method[" + scheduledMetadata.getMethod().getName() + "] cron() Expression[" + cron + "] Not Exist."));
            try {
                CronSequenceGenerator cronSequenceGenerator = StringUtils.isEmpty(zone)
                        ? new CronSequenceGenerator(value)
                        : new CronSequenceGenerator(value, TimeZone.getTimeZone(zone));
                scheduledExecuteMetadata.setType(ScheduleExecuteType.CRON);
                scheduledExecuteMetadata.setCronSequenceGenerator(cronSequenceGenerator);
                return scheduledExecuteMetadata;
            } catch (IllegalArgumentException e) {
                throw new ScheduledException("@Scheduled On Class[" + scheduledMetadata.getBean().getClass().getName()
                        + "] Method[" + scheduledMetadata.getMethod().getName() + "] cron() Expression[" + cron + "] Invalid.", e);
            }
        }
        throw new ScheduledException("@Scheduled On Class[" + scheduledMetadata.getBean().getClass().getName()
                + "] Method[" + scheduledMetadata.getMethod().getName() + "] content empty.");
    }

    /**
     * 校验定时调度执行元数据是否有效
     * @param scheduledExecuteMetadata 定时调度执行元数据
     */
    private void validScheduledExecuteMetadata(ScheduledExecuteMetadata scheduledExecuteMetadata) {
        if (scheduledExecuteMetadata == null) {
            throw new ScheduledException("ScheduledExecuteMetadata Must Not Be Null.");
        }
        if (scheduledExecuteMetadata.getType() == null) {
            throw new ScheduledException("ScheduledExecuteMetadata Type Must Not Be Null.");
        }
        ScheduledMetadata scheduledMetadata;
        if ((scheduledMetadata = scheduledExecuteMetadata.getScheduledMetadata()) == null
                || scheduledMetadata.getBean() == null || scheduledMetadata.getMethod() == null
                || scheduledMetadata.getScheduled() == null) {
            throw new ScheduledException("ScheduledMetadata Invalid.");
        }
        if (scheduledExecuteMetadata.getType() == ScheduleExecuteType.CRON && scheduledExecuteMetadata.getCronSequenceGenerator() == null) {
            throw new ScheduledException("Cron Scheduled Type, CronSequenceGenerator Must Not Be Null.");
        }
    }
}
